{% extends 'base.attached.class' %}

{% block content %}
var {{ class_name }} = function(vectors, coeffs, inters, weights, kernel, gamma, coef0, degree) {
    this.nClasses = weights.length;
    this.classes = new Array(this.nClasses);
    for (var i = 0; i < this.nClasses; i++) {
        this.classes[i] = i;
    }
    this.vectors = vectors;
    this.coeffs = coeffs;
    this.inters = inters;
    this.weights = weights;
    this.kernel = kernel.toUpperCase();
    this.gamma = gamma;
    this.coef0 = coef0;
    this.degree = degree;

    this.predict = function(features) {
        var kernels = new Array(vectors.length);
        var kernel;
        var i, j, k, d, l;
        switch (this.kernel) {
            case 'LINEAR':
                // <x,x'>
                for (i = 0; i < this.vectors.length; i++) {
                    kernel = 0.;
                    for (j = 0; j < this.vectors[i].length; j++) {
                        kernel += this.vectors[i][j] * features[j];
                    }
                    kernels[i] = kernel;
                }
                break;
            case 'POLY':
                // (y<x,x'>+r)^d
                for (i = 0; i < this.vectors.length; i++) {
                    kernel = 0.;
                    for (j = 0; j < this.vectors[i].length; j++) {
                        kernel += this.vectors[i][j] * features[j];
                    }
                    kernels[i] = Math.pow((this.gamma * kernel) + this.coef0, this.degree);
                }
                break;
            case 'RBF':
                // exp(-y|x-x'|^2)
                for (i = 0; i < this.vectors.length; i++) {
                    kernel = 0.;
                    for (j = 0; j < this.vectors[i].length; j++) {
                        kernel += Math.pow(this.vectors[i][j] - features[j], 2);
                    }
                    kernels[i] = Math.exp(-this.gamma * kernel);
                }
                break;
            case 'SIGMOID':
                // tanh(y<x,x'>+r)
                for (i = 0; i < this.vectors.length; i++) {
                    kernel = 0.;
                    for (j = 0; j < this.vectors[i].length; j++) {
                        kernel += this.vectors[i][j] * features[j];
                    }
                    kernels[i] = Math.tanh((this.gamma * kernel) + this.coef0);
                }
                break;
        }
        var starts = new Array(this.weights.length);
        for (i = 0; i < this.weights.length; i++) {
            if (i !== 0) {
                var start = 0;
                for (j = 0; j < i; j++) {
                    start += this.weights[j];
                }
                starts[i] = start;
            } else {
                starts[0] = 0;
            }
        }
        var ends = new Array(this.weights.length);
        for (i = 0; i < this.weights.length; i++) {
            ends[i] = this.weights[i] + starts[i];
        }
        if (this.nClasses === 2) {
            for (i = 0; i < kernels.length; i++) {
                kernels[i] = -kernels[i];
            }
            var decision = 0.;
            for (k = starts[1]; k < ends[1]; k++) {
                decision += kernels[k] * this.coeffs[0][k];
            }
            for (k = starts[0]; k < ends[0]; k++) {
                decision += kernels[k] * this.coeffs[0][k];
            }
            decision += this.inters[0];
            if (decision > 0) {
                return 0;
            }
            return 1;
        }
        var decisions = new Array(this.inters.length);
        for (i = 0, d = 0, l = this.weights.length; i < l; i++) {
            for (j = i + 1; j < l; j++) {
                var tmp = 0.;
                for (k = starts[j]; k < ends[j]; k++) {
                    tmp += this.coeffs[i][k] * kernels[k];
                }
                for (k = starts[i]; k < ends[i]; k++) {
                    tmp += this.coeffs[j - 1][k] * kernels[k];
                }
                decisions[d] = tmp + this.inters[d];
                d++;
            }
        }
        var votes = new Array(this.inters.length);
        for (i = 0, d = 0, l = this.weights.length; i < l; i++) {
            for (j = i + 1; j < l; j++) {
                votes[d] = decisions[d] > 0 ? i : j;
                d++;
            }
        }
        var amounts = new Array(this.nClasses).fill(0);
        for (i = 0, l = votes.length; i < l; i++) {
            amounts[votes[i]] += 1;
        }
        var classVal = -1, classIdx = -1;
        for (i = 0, l = amounts.length; i < l; i++) {
            if (amounts[i] > classVal) {
                classVal = amounts[i];
                classIdx= i;
            }
        }
        return this.classes[classIdx];
    }
};

var main = function () {
    if (typeof process !== 'undefined' && typeof process.argv !== 'undefined') {
        if (process.argv.length - 2 !== {{ n_features }}) {
            var IllegalArgumentException = function(message) {
                this.message = message;
                this.name = "IllegalArgumentException";
            }
            throw new IllegalArgumentException("You have to pass {{ n_features }} features.");
        }
    }

    // Features:
    var features = process.argv.slice(2);
    for (var i = 0; i < features.length; i++) {
        features[i] = parseFloat(features[i]);
    }

    // Model data:
    {{ vectors }}
    {{ coeffs }}
    {{ inters }}
    {{ weights }}

    // Estimator:
    var clf = new {{ class_name }}(vectors, coeffs, inters, weights, "{{ kernel }}", {{ gamma }}, {{ coef0 }}, {{ degree }});

    {% if is_test or to_json %}
    // Get JSON:
    console.log(JSON.stringify({
        "predict": clf.predict(features)
    }));
    {% else %}
    // Get class prediction:
    var prediction = clf.predict(features);
    console.log("Predicted class: #" + prediction);
    {% endif %}
}

if (require.main === module) {
    main();
}
{% endblock %}